﻿/************************************************************************************ 
 * Copyright (c) 2008-2011, Columbia University
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Columbia University nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY COLUMBIA UNIVERSITY ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * 
 * ===================================================================================
 * Author: Ohan Oda (ohan@cs.columbia.edu)
 * 
 * ===================================================================================
 * 
 * Modified by Hristo Hristov:
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 * 
 * Revision: 3
 * 
 * UPDATE on 14.08.11: -Support of Normal- and parallax-mapping by changing the 
 *                      vertex type to "NormalMappedVertex", which includes all
 *                      vertex information needed.
 *                     -Changed the string format of the parameters of the
 *                      "customShapeParameters" from "xx,yy" to "xx.yy", which
 *                      would otherwise cause a bug in GoblinXNA, when loading a 
 *                      scene from a xml file, because due to the comma a float 
 *                      would be recognized as to individual integers.
 *************************************************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using System.Globalization;

namespace NOVA.Graphics
{
    /// <summary>
    /// A sphere geometry primitive constructed with CustomMesh
    /// </summary>
    public class Sphere : PrimitiveModel
    {
        #region Properties

        public string ShapeProperties
        {
            get { return customShapeParameters; }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Creates a sphere of the given radius centered around the origin. The sphere 
        /// is subdivided around the Y axis into slices and along the Y axis into stacks 
        /// (similar to lines of longitude and latitude). 
        /// </summary>
        /// <param name="radius">Specifies the radius of the sphere.</param>
        /// <param name="slices">Specifies the number of subdivisions around the Y axis 
        /// (similar to lines of longitude). This has to be greater than 4 and less than 101.</param>
        /// <param name="stacks">Specifies the number of subdivisions along the Y axis 
        /// (similar to lines of latitude). This has to be greater than 4 and less than 101.</param>
        public Sphere(float radius, int slices, int stacks)
            : base(CreateSphere(radius, slices, stacks))
        {
            customShapeParameters = radius.ToString(CultureInfo.InvariantCulture.NumberFormat) + ", " +
                                    slices.ToString(CultureInfo.InvariantCulture.NumberFormat) + ", " +
                                    stacks.ToString(CultureInfo.InvariantCulture.NumberFormat);
        }

        public Sphere(params String[] xmlParams)
            : base(CreateSphere(float.Parse(xmlParams[0]), int.Parse(xmlParams[1]),
                int.Parse(xmlParams[2])))
        {
            customShapeParameters = xmlParams[0].ToString(CultureInfo.InvariantCulture.NumberFormat) + ", " +
                                    xmlParams[1].ToString(CultureInfo.InvariantCulture.NumberFormat) + ", " +
                                    xmlParams[2].ToString(CultureInfo.InvariantCulture.NumberFormat);
        }

        #endregion

        #region Private Static Methods

        private static CustomMesh CreateSphere(float radius, int slices, int stacks)
        {
            if (slices < 5)
                throw new ArgumentException("Cannot draw a sphere with slices less than 5");
            if (slices > 100)
                throw new ArgumentException("Cannot draw a sphere with slices greater than 100");
            if (stacks < 5)
                throw new ArgumentException("Cannot draw a sphere with stacks less than 5");
            if (stacks > 100)
                throw new ArgumentException("Cannot draw a sphere with stacks greater than 100");
            if (radius <= 0)
                throw new ArgumentException("Radius has to be greater than 0");

            CustomMesh mesh = new CustomMesh();
            
            double thai = 0, theta = 0;
            double thaiIncr = Math.PI / stacks;
            double thetaIncr = Math.PI * 2 / slices;
            int countB, countA;

            NormalMappedVertex[] vertices = new NormalMappedVertex[(int)((Math.PI - (3 * thaiIncr / 2))/thaiIncr +0.5f) * slices + 2];

            // Add sphere vertices
            int index = 0;
            for (countA = 0, thai = thaiIncr; thai < Math.PI - thaiIncr / 2; thai += thaiIncr, countA++)
            {
                for (countB = 0, theta = 0; countB < slices; theta += thetaIncr, countB++)
                {
                    vertices[index].Position = new Vector3((float)(radius * Math.Sin(thai) * Math.Cos(theta)),
                        (float)(radius * Math.Cos(thai)), (float)(radius * Math.Sin(thai) * Math.Sin(theta)));
                    vertices[index].Normal = Vector3.Normalize(vertices[index].Position);
                    vertices[index].Tangent = Vector4.Zero;
                    vertices[index].TexCoord = new Vector2(vertices[index].Normal.X / 2f + 0.5f, vertices[index].Normal.Y / 2f + 0.5f);
                    index++;
                }
            }

            // Add north pole vertex
            vertices[index] = new NormalMappedVertex(new Vector3(0,  radius, 0), Vector2.Zero, new Vector3(0, 1, 0), Vector4.Zero, Vector3.Zero);
            index++;

            // Add south pole vertex
            vertices[index] = new NormalMappedVertex(new Vector3(0, -radius, 0), Vector2.Zero, new Vector3(0, -1, 0), Vector4.Zero, Vector3.Zero);
            index++;

            // Calculate the tangent space basis vectors for the cube faces.
            Vector4 tangent;
            Vector3 binormal;

            for (int i = 0; i < vertices.Length; i += 3)
            {
                if (i + 2 < vertices.Length)
                {
                    NormalMappedVertex.CalcTangent(
                        ref vertices[i].Position,
                        ref vertices[i + 1].Position,
                        ref vertices[i + 2].Position,
                        ref vertices[i].TexCoord,
                        ref vertices[i + 1].TexCoord,
                        ref vertices[i + 2].TexCoord,
                        ref vertices[i].Normal,
                        out tangent,
                        out binormal);

                    vertices[i].Tangent = tangent;
                    vertices[i + 1].Tangent = tangent;
                    vertices[i + 2].Tangent = tangent;

                    vertices[i].Binormal = binormal;
                    vertices[i + 1].Binormal = binormal;
                    vertices[i + 2].Binormal = binormal;
                }
            }

            mesh.VertexDeclaration = NormalMappedVertex.VertexDeclaration;

            mesh.VertexBuffer = new VertexBuffer(Core.Device, typeof(NormalMappedVertex), vertices.Length, BufferUsage.None);
            mesh.VertexBuffer.SetData(vertices);

            List<short> indices = new List<short>();

            // Create the north and south pole area mesh
            for (int i = 0, j = (countA - 1) * slices; i < slices - 1; i++, j++)
            {
                indices.Add((short)(vertices.Length - 2));
                indices.Add((short)i);
                indices.Add((short)(i + 1));

                indices.Add((short)(vertices.Length - 1));
                indices.Add((short)(j + 1));
                indices.Add((short)j);
            }
            indices.Add((short)(vertices.Length - 2));
            indices.Add((short)(slices - 1));
            indices.Add(0);

            indices.Add((short)(vertices.Length - 1));
            indices.Add((short)((countA - 1) * slices));
            indices.Add((short)(vertices.Length - 3));

            // Create side of the sphere
            for (int i = 0; i < countA - 1; i++)
            {
                for (int j = 0; j < slices - 1; j++)
                {
                    indices.Add((short)(i * slices + j));
                    indices.Add((short)((i + 1) * slices + j));
                    indices.Add((short)((i + 1) * slices + j + 1));

                    indices.Add((short)(i * slices + j));
                    indices.Add((short)((i + 1) * slices + j + 1));
                    indices.Add((short)(i * slices + j + 1));
                }

                indices.Add((short)((i + 1) * slices - 1));
                indices.Add((short)((i + 2) * slices - 1));
                indices.Add((short)((i + 1) * slices));

                indices.Add((short)((i + 1) * slices - 1));
                indices.Add((short)((i + 1) * slices));
                indices.Add((short)(i * slices));
            }

            mesh.IndexBuffer = new IndexBuffer(Core.Device, typeof(short), indices.Count, BufferUsage.None);
            mesh.IndexBuffer.SetData(indices.ToArray());

            mesh.NumberOfVertices = vertices.Length;
            mesh.NumberOfPrimitives = indices.Count / 3;

            return mesh;
        }

        #endregion
    }
}
